/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cn.ac.iie.di.ban.news.server.check;

import cn.ac.iie.di.ban.news.server.check.checkers.Base64Checker;
import cn.ac.iie.di.ban.news.server.check.checkers.DomainChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.EmailChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.IntegerRangeChecher;
import cn.ac.iie.di.ban.news.server.check.checkers.IpChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.LongChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.MD5Checker;
import cn.ac.iie.di.ban.news.server.check.checkers.PhoneChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.QQChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.TimeDelayChecker;
import cn.ac.iie.di.ban.news.server.check.checkers.TimeLaterChecker;
import cn.ac.iie.di.datadock.rdata.exchange.client.core.REFieldType;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.log4j.Logger;

/**
 *
 * @author zwlin
 */
public class JsonChecker {

	private static final Logger logger = Logger.getLogger(JsonChecker.class);

	private static final HashMap<CheckDesc.CheckType, Checker> CHECK_MAP;

	static {
		CHECK_MAP = new HashMap<>();
		CHECK_MAP.put(CheckDesc.CheckType.Base64, new Base64Checker());
		CHECK_MAP.put(CheckDesc.CheckType.Domain, new DomainChecker());
		CHECK_MAP.put(CheckDesc.CheckType.IntegerRange, new IntegerRangeChecher());
		CHECK_MAP.put(CheckDesc.CheckType.Ip, new IpChecker());
		CHECK_MAP.put(CheckDesc.CheckType.Long, new LongChecker());
		CHECK_MAP.put(CheckDesc.CheckType.MD5, new MD5Checker());
		CHECK_MAP.put(CheckDesc.CheckType.TimeDelay, new TimeDelayChecker());
		CHECK_MAP.put(CheckDesc.CheckType.TimeLater, new TimeLaterChecker());
		CHECK_MAP.put(CheckDesc.CheckType.Email, new EmailChecker());
		CHECK_MAP.put(CheckDesc.CheckType.Phone, new PhoneChecker());
		CHECK_MAP.put(CheckDesc.CheckType.QQ, new QQChecker());

	}

	private static class TypeInfo {

		int type;
		String[] required;
		Map<String, CheckDesc> checks;
		Map<String, REFieldType> fields;

		public TypeInfo(int type, String[] required, Map<String, CheckDesc> checks, Map<String, REFieldType> fields) {
			this.type = type;
			this.required = required;
			this.checks = checks;
			this.fields = fields;
		}

		public TypeInfo() {
		}

	}

	HashMap<Integer, TypeInfo> Checkers;

	public JsonChecker() {
		Checkers = new HashMap<>();
	}

	public void reg(int type, String[] required, Map<String, CheckDesc> checks, Map<String, REFieldType> fields) {
		Checkers.put(type, new TypeInfo(type, required, checks, fields));
	}

	//testme
	public CheckResult check(int msg_type, JSONObject rec) {
		if (!Checkers.containsKey(msg_type)) {
			return new CheckResult("{\"error_record\":\"msg_type error.\"}");
		}

		TypeInfo typeInfo = Checkers.get(msg_type);

		String[] required = typeInfo.required;
		Map<String, CheckDesc> checks = typeInfo.checks;
		Map<String, REFieldType> fields = typeInfo.fields;

		HashMap<String, String> errorMap = new HashMap<>();
		//check required
		for (String s : required) {
			if (!rec.containsKey(s) || rec.get(s) == null) {
				errorMap.put(s, "required but not exist.");
			}
		}
		//check type
		for (String key : rec.keySet()) {
			REFieldType type = fields.get(key);
			//not mentioned
			if (type == null) {
				continue;
			}
			//mentioned
			switch (type) {
				case Int:
					try {
						rec.getInteger(key);
					} catch (Exception e) {
						errorMap.put(key, "type error, need Int.");
					}
					break;
				case Long:
					try {
						Long.parseLong(rec.getString(key));
					} catch (Exception e) {
						errorMap.put(key, "type error, need Long.");
					}
					break;
				case String:
					break;
				case Strings:
					try {
						JSONArray array = rec.getJSONArray(key);
						if (array != null && !array.isEmpty()) {
							for (int i = 0; i < array.size(); ++i) {
								array.getString(i);
							}
						}
					} catch (Exception e) {
						errorMap.put(key, "type error, need string array.");
					}
					break;
				default:
					break;
			}
		}
		//check detail
		for (String key : rec.keySet()) {
			//has error before
			if (errorMap.containsKey(key)) {
				continue;
			}
			if (checks.containsKey(key)) {
				CheckDesc checkDesc = checks.get(key);
				Object v;
				switch (fields.get(key)) {
					case Int:
						v = rec.getInteger(key);
						break;
					case Long:
						v = rec.getLong(key);
						break;
					case String:
						v = rec.getString(key);
						break;
					case Strings:
						JSONArray a = rec.getJSONArray(key);
						String[] strs = new String[a.size()];
						for (int i = 0; i < a.size(); ++i) {
							strs[i] = a.getString(i);
						}
						v = strs;
						break;
					default:
						v = rec.get(key);
				}
				if (v == null) {
					continue;
				}
				CheckResult rslt = null;
				Object param = null;
				switch (checkDesc.type) {
					case Base64:
						if (v instanceof String) {
						} else if (v instanceof String[]) {
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case Domain:
						if (v instanceof String) {
						} else if (v instanceof String[]) {
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case IntegerRange:
						if (v instanceof String) {
							param = checkDesc.param;
						} else if (v instanceof String[]) {
							param = checkDesc.param;
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case Ip:
						if (v instanceof String) {
						} else if (v instanceof String[]) {
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case Long:
						if (v instanceof Integer) {
							continue;
						} else if (v instanceof Long) {
							continue;
						} else if (v instanceof String) {
						} else if (v instanceof String[]) {
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case MD5:
						if (v instanceof String) {
							try {
								param = rec.getString((String) checkDesc.param);
							} catch (Exception e) {
								rslt = new CheckResult("field type error.detail:"
										+ ExceptionUtils.getStackTrace(e));
							}
						} else if (v instanceof String[]) {
							try {
								JSONArray a = rec.getJSONArray((String) checkDesc.param);
								if (v == null || ((String[]) v).length == 0) {
									rslt = new CheckResult();
								} else if (((String[]) v).length != a.size()) {
									rslt = new CheckResult("MD5 field size error.");
								} else {
									String[] strs = new String[a.size()];
									for (int i = 0; i < strs.length; ++i) {
										strs[i] = a.getString(i);
									}
									v = strs;
								}
							} catch (Exception e) {
								rslt = new CheckResult("field type error.detail:"
										+ ExceptionUtils.getStackTrace(e));
							}
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case TimeDelay:
						if (v instanceof String) {
							param = checkDesc.param;
						} else if (v instanceof String[]) {
							param = checkDesc.param;
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case TimeLater:
						if (v instanceof String) {
							param = rec.get(checkDesc.param);
						} else if (v instanceof String[]) {
							param = rec.get(checkDesc.param);
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case Email:
						if (v instanceof String) {
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case Phone:
						if (v instanceof String) {
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					case QQ:
						if (v instanceof String) {
						} else {
							rslt = new CheckResult("field type error.");
						}
						break;
					default:
						rslt = new CheckResult();
				}
				if (rslt == null) {
					try {
						Checker checker = CHECK_MAP.get(checkDesc.type);
						if (v.getClass().isArray()) {
							String r = "";
							if (param != null && param.getClass().isArray()) {
								for (int i = 0; i < Array.getLength(v); ++i) {
									CheckResult result = checker.check(Array.get(v, i), Array.get(param, i));
									if (!result.correct) {
										if (!r.isEmpty()) {
											r += ",";
										}
										r += "\"" + i + "\":\"" + result.reason + "\"";
									}
								}
							} else {
								for (int i = 0; i < Array.getLength(v); ++i) {
									CheckResult result = checker.check(Array.get(v, i), param);
									if (!result.correct) {
										if (!r.isEmpty()) {
											r += ",";
										}
										r += "\"" + i + "\":\"" + result.reason + "\"";
									}
								}
							}
							if (r.isEmpty()) {
								rslt = new CheckResult();
							} else {
								rslt = new CheckResult("{" + r + "}");
							}
						} else {
							rslt = checker.check(v, param);
						}
					} catch (Exception e) {
						rslt = new CheckResult("unknow check error." + ExceptionUtils.getStackTrace(e));
					}
				}
				if (!rslt.correct) {
					errorMap.put(key, rslt.reason);
				}
			}
		}
		if (errorMap.isEmpty()) {
			return new CheckResult();
		} else {
			JSONObject rslt = new JSONObject();
			for (Map.Entry<String, String> entry : errorMap.entrySet()) {
				if (REFieldType.Strings.equals(fields.get(entry.getKey()))) {
					rslt.put("error_" + entry.getKey(), JSONObject.parse(entry.getValue()));
				} else {
					rslt.put("error_" + entry.getKey(), entry.getValue());
				}
			}
			return new CheckResult(rslt.toJSONString());
		}
	}
}
